/**
o * @(#) TelenorCommunicator.java
 */
package com.klikmee.sms.core.base.communicator.telenor;

import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;

import org.apache.log4j.Logger;

import com.klikmee.sms.core.base.Core;
import com.klikmee.sms.core.base.communicator.Communicator;
import com.klikmee.sms.core.base.communicator.DeliveryReport;
import com.klikmee.sms.core.base.communicator.SendSmsIntercepter;
import com.klikmee.sms.core.base.communicator.telenor.longsms.TelenorLongSMSJoiner;
import com.klikmee.sms.core.base.dao.ProviderFacadeLocal;
import com.klikmee.sms.core.base.util.Constants;
import com.klikmee.sms.core.base.util.DeliveryReports;
import com.klikmee.sms.core.model.BulkMessage;
import com.klikmee.sms.core.model.IncomingMessage;
import com.klikmee.sms.core.model.OutgoingMessage;
import com.klikmee.sms.core.model.Provider;
import com.klikmee.sms.core.ws.telenor.client.delrep.DeliveryStatusType;
import com.klikmee.sms.core.ws.telenor.client.receivesms.SmsTypeExt;
import com.klikmee.sms.core.ws.telenor.client.sendsms.EndUserIdentifierBulk;
import com.klikmee.sms.core.ws.telenor.client.sendsms.Potpis;
import com.klikmee.sms.core.ws.telenor.client.sendsms.SendBulkSms;
import com.klikmee.sms.core.ws.telenor.client.sendsms.SendBulkSmsResponse;
import com.klikmee.sms.core.ws.telenor.client.sendsms.SendSMS;
import com.klikmee.sms.core.ws.telenor.client.sendsms.SendSMSResponse;

/**
 * @author makcro
 * 
 */
public class TelenorCommunicatorImpl implements Communicator {

	private Core core = null;
	private Provider provider = null;
	private URL wsdlURL = null;
	private String sendSmsEndpointOverrideAddress = null;
	private TelenorLongSMSJoiner longSMSJoiner = null;
	// telenor cpa web service clients
	private com.klikmee.sms.core.ws.telenor.client.sendsms.SmsParlayx sendSMSServiceEndpoint = null;
	private Potpis sendMessageCredentials = null;
	private ProviderFacadeLocal providerDao = null;
	public static final String CPA_RECEIVE_DATE_PATERN = "yyyy-MM-dd hh:mm:ss";
	public static final String LONGSMSHEADER6BIT = "0608";
	public static final String LONGSMSHEADER5BIT = "0500";
	private SimpleDateFormat receiveTimeFormater = null;
	public static final String MESSAGE_TYPE_SMS = "S";
	public static final String MESSAGE_TYPE_WAPPUSH = "P";
	public static final String MESSAGE_TYPE_LONG_SMS = "B";
	public static final String MESSAGE_TYPE_FLASH = "F";
	// helper tasks
	// <editor-fold desc="delivery status values">
	public static final String DELIVERED = "DELIVERED";
	public static final String UNSENT = "UNSENT";
	public static final String MSGC_REJECTED = "MSGC_REJECTED";
	public static final String UNDELIVERED = "UNDELIVERED";
	public static final String BLACKLIST = "BLACKLIST";
	public static final String BLACKBILL = "BLACKBILL";
	public static final String OVERFLOW = "OVERFLOW";
	public static final String POTPISERROR = "POTPISERROR";
	public static final String ADDRESSERROR = "ADDRESSERROR";
	public static final String SENDERNAMEERROR = "SENDERNAMEERROR";
	public static final String CHARGINGERROR = "CHARGINGERROR";
	public static final String MSGERROR = "MSGERROR";
	public static final String MSGTYPEERROR = "MSGTYPEERROR";
	public static final String NOCREDIT = "NOCREDIT";
	public static final String SYSTEMOVERFLOW = "SYSTEMOVERFLOW";
	public static final String VALIDITYTIMEERROR = "VALIDITYTIMEERROR";
	// </editor-fold>
	// time in milliseconds between two messages
	private int messageInterval = 0;
	private long timeOfLastMessageSending = 0;
	private Logger logger = Logger.getLogger(TelenorCommunicatorImpl.class);
	private String telenorSendingSpeedMsgPerSec = "10";

	private List<SendSmsIntercepter> sendSmsIntercepters = null;

	public TelenorCommunicatorImpl() {
	}

	private void initWSClients() {
		sendSMSServiceEndpoint = new com.klikmee.sms.core.ws.telenor.client.sendsms.SmsParlayxService(wsdlURL, new QName("http://telenor.cpa.parlayx.sms",
				"SmsParlayxService")).getSmsParlayxPort();
		// if endpoint override addrssi is set, use it
		if (this.sendSmsEndpointOverrideAddress != null) {
			BindingProvider sendsmsBP = (BindingProvider) sendSMSServiceEndpoint;
			Map<String, Object> sendsmsRequestContext = sendsmsBP.getRequestContext();
			sendsmsRequestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, sendSmsEndpointOverrideAddress);

			logger.info(String.format("Sendsms service url [%s]", this.sendSmsEndpointOverrideAddress));
		}
	}

	private void calculateMessageInterval() {
		int messagesPerSec = Integer.parseInt(this.telenorSendingSpeedMsgPerSec);
		messageInterval = 1000 / messagesPerSec;
	}

	public void init() {
		// load provider
		provider = providerDao.findByName(Constants.PROVIDER_TELENOR);
		// set speed limiter
		calculateMessageInterval();
		// init ws clients
		initWSClients();
		receiveTimeFormater = new SimpleDateFormat(CPA_RECEIVE_DATE_PATERN);

		logger.info("Telenor communicator successfuly started.");
	}

	public synchronized void sendMessage(OutgoingMessage outgoingMessage) {

		// limit sending rate at some number of messages per second
		slowDown();

		// int chargingCode =
		// Integer.parseInt(outgoingMessage.getService().getServicechargingcategories().iterator().next().getTelenorChargingCode());
		int chargingCode = Integer.parseInt(outgoingMessage.getPaymentInvoiceList().iterator().next().getServiceChargingCategory().getTelenorChargingCode());
		int telenorServiceName = Integer.parseInt(outgoingMessage.getPaymentInvoiceList().iterator().next().getServiceChargingCategory()
				.getTelenorServiceName());
		String messageType = resolveOutgoingMessageType(outgoingMessage);
		// long requestId = System.currentTimeMillis();

		String address = "tel:" + outgoingMessage.getToNumber();

		String message = outgoingMessage.getMessage();
		Long cpmsgId = outgoingMessage.getId();

		// try to get telenor eurequest. if it doesnt exist use 1l
		long eurequest = 1l;

		try {
			eurequest = Long.parseLong(outgoingMessage.getPaymentInvoiceList().iterator().next().getIncomingMessage().getTelenorEuRequest());
		}
		catch (Exception e) {
			logger.fatal("Failed to get telenor eureqest", e);

		}
		// determine outgoing message type.. is it bulk or ordinary sms

		boolean isThisSubscriptionMessage = outgoingMessage.getPaymentInvoiceList().iterator().next().getServiceChargingCategory().getSubscription();

		String wsRequestId = null;

		if (isThisSubscriptionMessage) {

			wsRequestId = sendBulkSMS(address, chargingCode, cpmsgId, eurequest, message, messageType, telenorServiceName, sendMessageCredentials);
		}
		else if (TelenorCommunicatorImpl.MESSAGE_TYPE_LONG_SMS.equals(messageType)) {

			wsRequestId = sendLongSMS(address, chargingCode, cpmsgId, eurequest, message, messageType, telenorServiceName, sendMessageCredentials);
		}
		else if (TelenorCommunicatorImpl.MESSAGE_TYPE_WAPPUSH.equals(messageType)) {

			wsRequestId = sendWapPush(address, chargingCode, cpmsgId, eurequest, message, outgoingMessage.getUrl(), messageType, telenorServiceName,
					sendMessageCredentials);
		}
		else {

			wsRequestId = sendSMS(address, eurequest, chargingCode, message, messageType, cpmsgId, telenorServiceName, sendMessageCredentials);
		}

		// we use providersMessageId field to store this value
		outgoingMessage.setProvidersMesssageId(wsRequestId);

		outgoingMessage.setOurOutGoingTime(new Date(System.currentTimeMillis()));
		// notify core that message was sent
		core.onMessageSent(outgoingMessage);

		logger.info("Message sent. details: id = " + outgoingMessage.getId() + ", to = " + outgoingMessage.getToNumber() + ", from = "
				+ outgoingMessage.getFromNumber() + ", serviceID = " + telenorServiceName + ", cc = " + chargingCode + ", wsRequestId = " + wsRequestId);

	}

	public void sendBulkMessage(BulkMessage bulkMessage) {
		String address = "tel:" + bulkMessage.getToNumber();
		long charging = Long.parseLong(bulkMessage.getChargingCategory().getTelenorChargingCode());
		Long cpmsgId = bulkMessage.getId();
		Long eurequest = 0l;
		String message = bulkMessage.getText();

		long senderName = Integer.parseInt(bulkMessage.getChargingCategory().getService().getTelenorServiceId());
		String wsRequestId = this.sendBulkSMS(address, charging, cpmsgId, eurequest, message, TelenorCommunicatorImpl.MESSAGE_TYPE_SMS, senderName,
				this.sendMessageCredentials);
		bulkMessage.setPrividersId(wsRequestId);

		core.onBulkMessageSent(bulkMessage);
	}

	private void slowDown() {
		long now = System.currentTimeMillis();
		// slow down messages to MTS specified speed
		if (now - messageInterval < timeOfLastMessageSending) {
			long waitTime = messageInterval - (now - timeOfLastMessageSending);
			Boolean x = Boolean.TRUE;
			synchronized (x) {
				try {
					x.wait(waitTime);
				}
				catch (Exception e) {
				}
			}
		}
		// update time of last sending
		timeOfLastMessageSending = System.currentTimeMillis();
	}

	private String sendWapPush(String address, int chargingCode, Long cpmsgId, long eurequestId, String message, String url, String messageType,
			int telenorServiceName, Potpis potpis) {

		String wappushUDH = "0605040B8423F0";
		String dataWrapping = "DC0601AE02056A0045C60C03";
		String endingStart = "000103";
		String endingEnd = "000101";

		String title_hex = getHex(message.getBytes());
		String url_hex = getHex(url.getBytes());

		String wappushMessageHEX = wappushUDH + dataWrapping + url_hex + endingStart + title_hex + endingEnd;
		// build the message body
		SendBulkSms sendBulkSms_params = new SendBulkSms();

		EndUserIdentifierBulk endUserIdentifierBulk = new EndUserIdentifierBulk();
		endUserIdentifierBulk.setAddress(address);
		endUserIdentifierBulk.setCharging(chargingCode);
		endUserIdentifierBulk.setCpmsgid(cpmsgId);
		endUserIdentifierBulk.setEurequest(eurequestId);
		endUserIdentifierBulk.setMessage(wappushMessageHEX);
		endUserIdentifierBulk.setMsgtip(messageType);
		endUserIdentifierBulk.setSenderName(telenorServiceName);

		sendBulkSms_params.getBulkDestinationAddressSet().add(endUserIdentifierBulk);
		sendBulkSms_params.setCprequestid(cpmsgId);

		// send message
		SendBulkSmsResponse response = sendSMSServiceEndpoint.sendBulkSms(sendBulkSms_params, potpis);
		String wsreqId = response.getReturn();

		return wsreqId;
	}

	private String sendLongSMS(String address, int chargingCode, Long cpmsgId, long l, String message, String messageType, int telenorServiceName, Potpis potpis) {

		// split message
		ArrayList<String> messageParts = splitMessage(message);
		int longMessageID = new Random().nextInt(254) + 1;
		String longMessageIdHex = Integer.toHexString(longMessageID);

		// build the message body
		SendBulkSms sendBulkSms_params = new SendBulkSms();

		for (int i = 0; i < messageParts.size(); i++) {
			EndUserIdentifierBulk endUserIdentifierBulk = new EndUserIdentifierBulk();

			String messagePart = messageParts.get(i);
			String hexMessageText = getHex(messagePart.getBytes());
			String messageWithUDH = "050003" + longMessageIdHex + "0" + messageParts.size() + "0" + (i + 1) + hexMessageText;

			endUserIdentifierBulk.setMessage(messageWithUDH);

			endUserIdentifierBulk.setAddress(address);
			endUserIdentifierBulk.setCharging(chargingCode);
			endUserIdentifierBulk.setCpmsgid(cpmsgId);
			endUserIdentifierBulk.setEurequest(l);

			endUserIdentifierBulk.setMsgtip(messageType);
			endUserIdentifierBulk.setSenderName(telenorServiceName);

			sendBulkSms_params.getBulkDestinationAddressSet().add(endUserIdentifierBulk);

		}

		sendBulkSms_params.setCprequestid(cpmsgId);

		// send message
		SendBulkSmsResponse response = sendSMSServiceEndpoint.sendBulkSms(sendBulkSms_params, potpis);
		String wsreqId = response.getReturn();

		return wsreqId;
	}

	public String getHex(byte[] raw) {
		String HEXES = "0123456789ABCDEF";

		if (raw == null) {
			return null;
		}
		final StringBuilder hex = new StringBuilder(2 * raw.length);
		for (final byte b : raw) {
			hex.append(HEXES.charAt((b & 0xF0) >> 4)).append(HEXES.charAt((b & 0x0F)));
		}
		return hex.toString();
	}

	public ArrayList<String> splitMessage(String longMessage) {
		ArrayList<String> parts = new ArrayList<String>();
		int partSize = 153;
		// characters total
		int messageLength = longMessage.length();
		// index of last split
		int delimiter = 0;
		int rest = messageLength - delimiter;
		// if rest of the message is bigger then part size, then split the rest
		// and move delimiter
		while (rest > partSize) {
			String part = longMessage.substring(delimiter, delimiter + partSize);
			parts.add(part);
			delimiter += partSize;
			rest = messageLength - delimiter;
		}
		// if the rest is bigger then 0 then add it as final part
		if (rest > 0) {
			String finalPart = longMessage.substring(delimiter);
			parts.add(finalPart);
		}

		return parts;
	}

	private String sendSMS(String address, Long eurequest, int chargingCode, String message, String messageType, long cpreqId, long telenorServiceName,
			Potpis potpis) {

		SendSMS sendSMSparams = new SendSMS();

		// set receiver number
		com.klikmee.sms.core.ws.telenor.client.sendsms.EndUserIdentifier endUserIdentifier = new com.klikmee.sms.core.ws.telenor.client.sendsms.EndUserIdentifier();

		endUserIdentifier.setAddress(address);
		endUserIdentifier.setEurequest(eurequest);
		sendSMSparams.getDestinationAddressSet().add(endUserIdentifier);

		// set other parameters
		sendSMSparams.setCharging(chargingCode);
		sendSMSparams.setMessage(message);
		sendSMSparams.setMsgtype(messageType);
		sendSMSparams.setCprequestid(cpreqId);
		sendSMSparams.setSenderName(telenorServiceName);

		// send message to Telenor CPA. Method returns wsRequestId which is
		// usede
		// to get delivery status for this message.
		// So, we need to save it for further use.
		SendSMSResponse response = sendSMSServiceEndpoint.sendSMS(sendSMSparams, potpis);

		String wsRequestId = response.getReturn();
		return wsRequestId;
	}

	private String sendBulkSMS(String address, long charging, long cpmsgId, Long eurequest, String message, String msgType, long senderName, Potpis potpis) {

		SendBulkSms sendBulkSms_params = new SendBulkSms();
		// SendNoChargeBulkSms sendNoChargeBulkSms = new SendNoChargeBulkSms();

		EndUserIdentifierBulk endUserIdentifierBulk = new EndUserIdentifierBulk();
		endUserIdentifierBulk.setAddress(address);
		endUserIdentifierBulk.setCharging(charging);
		endUserIdentifierBulk.setCpmsgid(cpmsgId);
		endUserIdentifierBulk.setEurequest(eurequest);
		endUserIdentifierBulk.setMessage(message);
		endUserIdentifierBulk.setMsgtip(msgType);
		endUserIdentifierBulk.setSenderName(senderName);

		sendBulkSms_params.getBulkDestinationAddressSet().add(endUserIdentifierBulk);
		sendBulkSms_params.setCprequestid(cpmsgId);

		// sendNoChargeBulkSms.getBulkDestinationAddressSet().add(endUserIdentifierBulk);
		// sendNoChargeBulkSms.setCprequestid(cpmsgId);

		SendBulkSmsResponse response = sendSMSServiceEndpoint.sendBulkSms(sendBulkSms_params, potpis);
		String wsreqId = response.getReturn();

		// SendNoChargeBulkSmsResponse response =
		// sendSMSServiceEndpoint.sendNoChargeBulkSms(sendNoChargeBulkSms,
		// potpis);
		// String wsreqId = response.getReturn();
		return wsreqId;

	}

	public void onMessagesReceived(List<SmsTypeExt> typeExts) {

		// iterate trough message list
		for (int i = 0; i < typeExts.size(); i++) {
			SmsTypeExt currentMessage = typeExts.get(i);

			// check if concatenated message seq starts
			String text = currentMessage.getMessage();

			// check if this is a part of the long message
			if (text.startsWith(LONGSMSHEADER5BIT) || text.startsWith(LONGSMSHEADER6BIT)) {
				longSMSJoiner.processPart(currentMessage);

			}
			else {
				// if not long sms, just forward it to core
				forwardMessageToCore(currentMessage);
			}

		}

	}

	private void forwardMessageToCore(SmsTypeExt smsTypeExt) {
		String shortCode = smsTypeExt.getShortid();
		String receivedTime = smsTypeExt.getTime();
		String text = smsTypeExt.getMessage();
		String dataCoding = smsTypeExt.getDc();
		String emsc = smsTypeExt.getEsmc();

		com.klikmee.sms.core.ws.telenor.client.receivesms.EndUserIdentifier senderData = smsTypeExt.getSenderAddress();
		String senderId = senderData.getAddress();
		// trim 'tel:' prefix
		String customerPhoneNumber = senderId.substring(senderId.indexOf(":") + 1);
		String euRequest = String.valueOf(senderData.getEurequest());

		Date providersTime = null;

		try {
			providersTime = receiveTimeFormater.parse(receivedTime);
		}
		catch (ParseException e) {

			logger.fatal("Could not parse telenor message received time " + receivedTime + ". Using current timestamp instead.");
			providersTime = new Date();
		}

		IncomingMessage incomingMessage = new IncomingMessage();
		incomingMessage.setFromNumber(customerPhoneNumber);
		incomingMessage.setMessage(text);
		incomingMessage.setProvidersIncomingTime(providersTime);
		incomingMessage.setToNumber(shortCode);
		incomingMessage.setOurIncomingTime(new Date(System.currentTimeMillis()));
		// this field will be used for storing wsrequest id after message is
		// sent
		incomingMessage.setProviderMessageId("");

		incomingMessage.setDataCoding(dataCoding);
		incomingMessage.setTelenorESMC(emsc);
		incomingMessage.setTelenorEuRequest(euRequest);
		incomingMessage.setProvider(provider);
		incomingMessage.setTypeOfMessage(Constants.MESSAGE_TYPE_SMS);

		core.enqueueIncomingMessage(incomingMessage);

		logger.info("Incomming message processed. details: shortId = " + shortCode + ", from = " + customerPhoneNumber + ", eurequest = " + euRequest);
	}

	@Override
	public boolean canHandleBulkMessage(String receiver) {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * <p>
	 * Translates our message types defined in ConstantManager class into
	 * telenor message types. If outgoing message text length is longer then 160
	 * characters then type 'Long sms' is returned.
	 * </p>
	 * 
	 * @param outgoingMessage
	 * @return
	 */
	private String resolveOutgoingMessageType(OutgoingMessage outgoingMessage) {
		String result = null;

		// first check for message text length
		if (outgoingMessage.getMessage().length() > 160) {
			result = MESSAGE_TYPE_LONG_SMS;
		} // probe for other message types
		else {

			switch (outgoingMessage.getTypeOfMessage()) {

			case Constants.MESSAGE_TYPE_SMS:
				result = TelenorCommunicatorImpl.MESSAGE_TYPE_SMS;
				break;

			case Constants.MESSAGE_TYPE_WAP_PUSH_SERVICE_LOADED:
			case Constants.MESSAGE_TYPE_WAP_PUSH_SERVICE_INDICATED:
				result = TelenorCommunicatorImpl.MESSAGE_TYPE_WAPPUSH;
				break;

			default:
				break;
			}

		}

		return result;

	}

	@Override
	public List<SendSmsIntercepter> getSendSmsIntercepters() {
		// TODO Auto-generated method stub
		return null;
	}

	public void onDeliveryReports(List<DeliveryStatusType> deliveryReports) {
		for (DeliveryStatusType deliveryStatusType : deliveryReports) {

			long messageId = deliveryStatusType.getCpmsgid();
			String status = deliveryStatusType.getDeliveryStatus().getValue();

			DeliveryReport deliveryReport = new DeliveryReport();
			deliveryReport.setMessageId(messageId);
			int deliveryStatus = resovleDeliveryStatus(status);

			deliveryReport.setStatus(deliveryStatus);
			deliveryReport.setProvider(provider);

			core.enqueueStatusReport(deliveryReport);

			logger.debug("delivery procesed. details: cpMessageId = " + messageId + ", status = " + status + ", resolvedStatus = " + deliveryStatus);
		}

	}

	private int resovleDeliveryStatus(String telenorString) {
		if (TelenorCommunicatorImpl.DELIVERED.equalsIgnoreCase(telenorString)) {
			return DeliveryReports.DELIVERED;
		}
		else if (TelenorCommunicatorImpl.UNSENT.equalsIgnoreCase(telenorString)) {
			return DeliveryReports.Telenor.UNSENT;
		}
		else if (TelenorCommunicatorImpl.MSGC_REJECTED.equalsIgnoreCase(telenorString)) {
			return DeliveryReports.Telenor.MSGC_REJECTED;
		}
		else if (TelenorCommunicatorImpl.UNDELIVERED.equalsIgnoreCase(telenorString)) {
			return DeliveryReports.Telenor.UNDELIVERED;
		}
		else if (TelenorCommunicatorImpl.BLACKLIST.equalsIgnoreCase(telenorString)) {
			return DeliveryReports.Telenor.BLACKLIST;
		}
		else if (TelenorCommunicatorImpl.BLACKBILL.equalsIgnoreCase(telenorString)) {
			return DeliveryReports.Telenor.BLACKBILL;
		}
		else if (TelenorCommunicatorImpl.OVERFLOW.equalsIgnoreCase(telenorString)) {
			return DeliveryReports.Telenor.OVERFLOW;
		}
		else if (TelenorCommunicatorImpl.NOCREDIT.equalsIgnoreCase(telenorString)) {
			return DeliveryReports.Telenor.NOCREDIT;
		}

		return -1;

	}

	public void setCore(Core core) {
		this.core = core;
	}

	public void setSendMessageCredentials(Potpis sendMessageCredentials) {
		this.sendMessageCredentials = sendMessageCredentials;
	}

	public void setProviderDao(ProviderFacadeLocal providerDao) {
		this.providerDao = providerDao;
	}

	public void setTelenorSendingSpeedMsgPerSec(String telenorSendingSpeedMsgPerSec) {
		this.telenorSendingSpeedMsgPerSec = telenorSendingSpeedMsgPerSec;
	}

	public void setEndpointOverrideAddress(String endpointOverrideAddress) {
		this.sendSmsEndpointOverrideAddress = endpointOverrideAddress;
	}

	public void setWsdlURL(URL wsdlURL) {
		this.wsdlURL = wsdlURL;
	}

	public void setLongSMSJoiner(TelenorLongSMSJoiner longSMSJoiner) {
		this.longSMSJoiner = longSMSJoiner;
	}

	public URL getWsdlURL() {
		return wsdlURL;
	}

	public void setSendSmsEndpointOverrideAddress(String sendSmsEndpointOverrideAddress) {
		this.sendSmsEndpointOverrideAddress = sendSmsEndpointOverrideAddress;
	}

	public void setSendSmsIntercepters(List<SendSmsIntercepter> sendSmsIntercepters) {
		this.sendSmsIntercepters = sendSmsIntercepters;
	}

	@Override
	public Provider getProvider() {
		// TODO Auto-generated method stub
		return null;
	}

}
